/* LCD module */
 
#include <p18cxxx.h>
#include <delays.h>
#include "define.h"
 
/* LCD display lines */
#define L0_p1     0x80            /** Line 0 (upper line) with positions 1 to 16 */
#define L0_p2     0x81
#define L0_p3     0x82
#define L0_p4     0x83
#define L0_p5     0x84
#define L0_p6     0x85
#define L0_p7     0x86
#define L0_p8     0x87
#define L0_p9     0x88
#define L0_p10   0x89
#define L0_p11   0x8A
#define L0_p12   0x8B
#define L0_p13   0x8C
#define L0_p14   0x8D
#define L0_p15   0x8E
#define L0_p16   0x8F
 
#define L1_p1     0xC0           /** Line 1 (bottom line) with positions 1 to 16 */
#define L1_p2     0xC1
#define L1_p3     0xC2
#define L1_p4     0xC3
#define L1_p5     0xC4
#define L1_p6     0xC5
#define L1_p7     0xC6
#define L1_p8     0xC7
#define L1_p9     0xC8
#define L1_p10     0xC9
#define L1_p11     0xCA
#define L1_p12     0xCB
#define L1_p13     0xCC
#define L1_p14     0xCD
#define L1_p15     0xCE
#define L1_p16     0xCF
 
/* Register selection and Read/Write inputs */
#define READ     1                           /** Read Operation */
#define WRITE    0                           /** Write Operation */
#define INSTR    0                           /** Instruction register is selected */
#define DATA    1                           /** Data Register is selected */
 
 
/* LCD settings */
#define FUNCTION_SET    0x38                /** 8 bit bus, 2 lines */
#define DISPLAY_OFF     0x08                /** Display control BitD is low. Entire display is turned off. Bit3 set*/
#define DISPLAY_ON      0x0F                /** Display control BitD is high. Entire display is turned on. Bit3 set. Cursor control bit and blink bit are set to high(turned on)*/
#define CLEAR_DISPLAY   0x01                /** Clear all the display data. Bit0 set */
#define ENTRY_MODE      0x06                /** Increment mode, Entire display Shift off, Bit2 set */
 
/* PSP pins */
#define LCD_DATA    PORTD                   /** LCD 8 data bits */
#define LCD_D_DIR    TRISD                   /** PORTD Data direction register. Setting a TRISD bit(=1) will make the corresponding PORTD pin an input, while clearing it (=0) will make the pin an output */
#define LCD_RS        PORTEbits.RE0           /** LCD Register Select control line */
#define    LCD_RS_DIR    TRISEbits.TRISE0        /** Register Select Direction Control bit. 1=Input, 0=Output.*/
#define LCD_RW        PORTEbits.RE1           /** LCD Read/Write control line */
#define    LCD_RW_DIR    TRISEbits.TRISE1        /** Read/Write Direction Control bit. 1=Input, 0=Output.*/
#define LCD_E        PORTEbits.RE2           /** LCD Enable control line */
#define    LCD_E_DIR    TRISEbits.TRISE2        /** Enable Direction Control bit. 1=Input, 0=Output.*/
 
 
/* Variables */
char LCDBuffer[O_BUFSIZE];                  /** Output buffer */
unsigned char writeIndex = 0;               /** Write Index for buffer */
unsigned char readIndex = 0;                /** Read Index for buffer */
unsigned char LCDWantsToSend = FALSE;       /** Flag indicating data to be sent */
 
/* Prototypes */
void initialise_LCD (void);
void Instruction_LCD(unsigned char value);
void BF_LCD(void);
unsigned char get_DDRAM (void);
void putchar_LCD (unsigned char cval);
void send_LCD (unsigned char dispval);
void string_LCD(unsigned char *pointer);
void sendNext(void);
unsigned char isBusy(void);
 
/**
* Function that initialises the LCD by instruction to an 8-bit interface mode.
* @pre LCD has power.
*/
 
void initialise_LCD (void)
{
    TRISE = 0x00;                            /* Configuration for output on PORTD and PORTE */
    TRISD = 0x00;
    ADCON1 = 0b00000110;                     /* The A/D port configuration bits PCFG2:PCFG0 (ADCON1<2:0>) SET,to configure pins RE2:RE0 as digital I/O */
    PORTD = 0x00;
    LCD_E_DIR = 0;
    LCD_RS_DIR = 0;
    LCD_RW_DIR = 0;
 
    /* Initialisation using delays.h */
 
    Delay1KTCYx(120);                        /* Power on delay of 30ms */
    Instruction_LCD(FUNCTION_SET);           /* 8 bit interface, 2-lines */
    Delay10TCYx(16);                         /* Delay of 39 microseconds */
    Instruction_LCD(DISPLAY_ON);             /* display, cursor and blink on */
    Delay10TCYx(16);                         /* Delay of 39 microseconds */
    Instruction_LCD(CLEAR_DISPLAY);          /* Clear the display and return cursor home */
    Delay100TCYx(62);                        /* Delay of 1.53 ms */
    Instruction_LCD(ENTRY_MODE);             /* Entry Mode Sets: cursor moves to the righ and display appears stationary */
}
/**
* Function that sends an instruction to the LCD.
* @param value The command character to send
* @pre LCD has been initialised
*/
 
void Instruction_LCD(unsigned char value)
{
  BF_LCD();                                /* check through Busy Flag that the LCD is ready to go */
  LCD_RS = INSTR;                          /* selects Instruction register (IR) */
  LCD_RW = WRITE;                          /* Sets write mode */
  LCD_E = 1;                               /* Enable signal */
  LCD_DATA = value;                           /* send out the command */
  Nop();
  LCD_E = 0;                                /* external write cycle is complete */
}
 
/**
* Function that detects the LCD Busy Flag(BF)before executing the next instrunction.
* @pre The LCD must be initalised.
* @post The LCD is not busy.
*/
 
void BF_LCD(void)
{
     char check_bf = 1;
    LCD_D_DIR = 0xFF;                   /* configure LCD data bus for input */
    LCD_RS = INSTR;                     /* Set LCD for instruction mode (IR) */
    LCD_RW = READ;                         /* Set to read busy flag */
    while (check_bf)
    {
        LCD_E = 1;                         /* Enable signal */
        check_bf = LCD_DATA;            /* Read busy flag + DDram address */
        LCD_E     = 0;                     /* complete a read cycle */
        check_bf = check_bf & 0x80;     /* check Busy Flag, Busy = 1. DBit7 used for Busy Flag output */
    }
    LCD_D_DIR     = 0x00;                 /* configure LCD data bus for output */
}
 
/**
* Function that reads the DDRAM value.
* @return The DDRAM data bits 0-6
* @pre The LCD must be initalised.
*/
 
unsigned char get_DDRAM (void)
{
    char temp;
    LCD_D_DIR = 0xFF;                  /* configure LCD data port for input */
    LCD_RS = INSTR;                       /* select IR register */
    LCD_RW = READ;                       /* setup to read busy flag */
    LCD_E = 1;                            /* LCD E-line to high */
    temp = LCD_DATA & 0x7F;            /* read DDRAM address and set busy flag to low */
 
    LCD_E = 0;                            /* LCD E-line to low */
    return temp;
}
 
/**
* Function that sends character to send_LCD. It toggles the data between the top and bottom display lines of the LCD.
* The 'end of line' symbol sets the data to display in the bottom row, whilst the 'carriage return' symbol sets it back to the top one.
* @param cval The character to be sent.
* @pre LCD is fully initialised
*/
 
void putchar_LCD (unsigned char cval)
{
    BF_LCD();                                              /* Wait for LCD to be ready */
    if(cval == '\n')                                     /* Line feed means set to row 2 */
    {
         Instruction_LCD(L1_p1);                          /* set cursor to column 1 row 2 */
        return;
    }
    if(cval == '\r')                                     /* Carriage return means set to row 1 */
    {
        Instruction_LCD(L0_p1);                             /* set cursor to column 1 row 1 */
        return;
    }
    send_LCD(cval);
}
 
/**
* Function that transmits a character to LCD.
* @param dispval The character to be sent.
* @pre The LCD must not be busy and it must be initialised.
* @post The LCD busy flag is set until it finishes
*/
 
void send_LCD (unsigned char dispval)
{
    LCD_RS = DATA;                 /* Set LCD in data mode */
    LCD_RW = WRITE;                /* Set LCD in write mode */
    LCD_E = 1;                     /* LCD E-line High */
    LCD_DATA = dispval;            /* Send data to LCD */
    LCD_E = 0;                     /* LCD E-line to Low */
}
 
/**
 * Stores a string in the LCDbuffer to be sent to the LCD.
 * Notes: Calling this method to rapidly with too much data will cause
 * the buffer to overflow, and data may not be transmitted properly.
 * @param pointer The unsigned char* pointing to the string.
 * @pre The LCD is properly initialised
 * @post The string is added to the LCDbuffer
 */
 
void string_LCD(unsigned char* pointer)
{
    while (*pointer != '\0')
    {
 
        LCDBuffer[writeIndex] = *pointer;
        writeIndex ++;
        if(writeIndex == O_BUFSIZE)            /* End of circular buffer */
        {
            writeIndex = 0;                    /* Loop round */
        }
        pointer++;                            /* Next char */
    }
    LCDWantsToSend = TRUE;
}
 
/**
 * Sends the next character stored in the LCDBuffer.
 * @pre The LCD is not busy. Important to check this first.
 * @post The character is sent, and the buffer moves along. If there are no more characters to send in the buffer, then LCDWantsToSend will be
 * set to FALSE.
 */
 
void sendNext(void)
{
    if(!LCDWantsToSend) return;             /* If there's nothing to send, don't. */
    putchar_LCD(LCDBuffer[readIndex]);
    readIndex++;
    if(readIndex == O_BUFSIZE)             /* End of circular buffer */
    {
        readIndex = 0;                     /* Loop round */
    }
    if(readIndex == writeIndex)             /* If the read catches up with the write */
    {
        LCDWantsToSend = FALSE;
    }
}
 
/**
 * Gets whether or not the LCD is busy
 * @return boolean An unsigned char of 0 or !0 (specifically 0x80)
 */
 
unsigned char isBusy(void)
{
    char check_bf = 1;
    LCD_D_DIR = 0xFF;                   /* configure LCD data bus for input */
    LCD_RS = INSTR;                     /* Set LCD for instruction mode (IR) */
    LCD_RW = READ;                         /* Set to read busy flag */
    LCD_E = 1;                             /* Enable signal */
    check_bf = LCD_DATA;                /* Read busy flag + DDram address */
    LCD_E     = 0;                         /* complete a read cycle */
    check_bf = check_bf & 0x80;         /* check Busy Flag, Busy = 1. DBit7 used for Busy Flag output */
    LCD_D_DIR = 0x00;                   /* configure LCD data bus for output */
    return check_bf;                    /* check_bf = 0 or 0x80 */
}